package com.learn.sms.core.client.impl.aliyun;

import cn.hutool.core.lang.Assert;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.*;
import com.aliyun.teaopenapi.models.Config;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.learn.sms.common.core.KeyValue;
import com.learn.sms.common.util.collection.MapUtils;
import com.learn.sms.common.util.json.JsonUtils;
import com.learn.sms.core.client.SmsCommonResult;
import com.learn.sms.core.client.dto.SmsReceiveRespDTO;
import com.learn.sms.core.client.dto.SmsSendRespDTO;
import com.learn.sms.core.client.dto.SmsTemplateRespDTO;
import com.learn.sms.core.client.impl.AbstractSmsClient;
import com.learn.sms.core.property.SmsChannelProperties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.learn.sms.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static com.learn.sms.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
import static com.learn.sms.core.constant.SmsFrameworkErrorCodeConstants.EXCEPTION;

/**
 * @author : Yu.Tan
 * @date : 2023/7/26 10:52
 * @desc : this class for 阿里短信客户端的实现类
 **/
@Slf4j
public class AliYunSmsClient extends AbstractSmsClient {
    /**
     * REGION
     */
    private static final String ENDPOINT = "dysmsapi.aliyuncs.com";
    /**
     * 阿里云客户端
     */
    private volatile Client client;


    public AliYunSmsClient(SmsChannelProperties properties) {
        super(properties, new AliYunSmsCodeMapping());
        Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空");
        Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
    }

    @Override
    protected void doInit() throws Exception {
        Config config = new Config()
                .setAccessKeyId(properties.getApiKey())
                .setAccessKeySecret(properties.getApiSecret())
                .setEndpoint(ENDPOINT);
        client = new Client(config);
    }

    @Override
    protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {
        return invoke(() -> buildSendSmsRequest(sendLogId, mobile, apiTemplateId, templateParams)
                , this::sendSms
                , response -> {
                    SendSmsResponseBody body = response.getBody();
                    return SmsCommonResult.build(body.getCode(), body.getMessage(), body.getRequestId(), new SmsSendRespDTO().setSerialNo(body.getBizId()), codeMapping);
                });
    }

    public SendSmsResponse sendSms(SendSmsRequest request) throws Exception {
        return client.sendSms(request);
    }

    public SendSmsRequest buildSendSmsRequest(Long sendLogId, String mobile, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
        return new SendSmsRequest()
                .setPhoneNumbers(mobile)
                .setSignName(properties.getSignature())
                .setTemplateCode(apiTemplateId)
                .setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)))
                .setOutId(String.valueOf(sendLogId));
    }

    protected void doSendBatchSms(Long sendLogId, String mobileJson, String apiTemplateId, String templateParamJson) throws Throwable {
        SendBatchCardSmsRequest request = new SendBatchCardSmsRequest().setPhoneNumberJson(mobileJson)
                .setSignNameJson(properties.getSignature())
                .setTemplateCode(apiTemplateId)
                .setTemplateParamJson(templateParamJson)
                .setOutId(String.valueOf(sendLogId));
        SendBatchCardSmsResponse smsResponse = client.sendBatchCardSms(request);
        SendBatchCardSmsResponseBody body = smsResponse.getBody();
        String code = body.getCode();
        String requestId = body.getRequestId();
        String bizId = null;
        SmsSendRespDTO smsSendRespDTO = new SmsSendRespDTO();
        // 请求成功的情况下
        if (Objects.equals(body.getCode(), "OK")) {
            smsSendRespDTO.setSerialNo(bizId);
        }
        // 拼接结果
//        return SmsCommonResult.build(code, message, requestId, smsSendRespDTO, codeMapping);

    }


    /***********************发送查询******************************/
    /**
     * 可以通过本接口查询短信发送量详情，包括短信发送时间、短信发送成功条数、接收回执条数等。如果指定日期短信发送量较大，可以分页查看。
     * 指定每页显示的短信详情数量和查看的页数，即可分页查看发送记录。
     * <p>
     * QPS限制
     * 本接口的单用户QPS限制为20次/秒。超过限制，API调用会被限流，这可能会影响您的业务，请合理调用。
     *
     * @param isGlobe
     * @param startDate
     * @param endDate
     * @param pageIndex
     * @param pageSize
     * @param templateType
     * @param signName
     * @throws Throwable
     */
    protected void querySendStatistics(Integer isGlobe, String startDate, String endDate, Integer pageIndex, Integer pageSize, Integer templateType, String signName) throws Throwable {
        QuerySendStatisticsRequest request = new QuerySendStatisticsRequest()
                .setIsGlobe(isGlobe)
                .setStartDate(startDate)
                .setEndDate(endDate)
                .setPageIndex(pageIndex)
                .setPageSize(pageSize)
                .setTemplateType(templateType)
                .setSignName(signName);
        QuerySendStatisticsResponse response = client.querySendStatistics(request);
        // 请求成功的情况下
        if (Objects.equals(response.getBody().getCode(), "OK")) {
            QuerySendStatisticsResponseBody body = response.getBody();
            QuerySendStatisticsResponseBody.QuerySendStatisticsResponseBodyData bodyData = body.getData();
            // TODO: 2023/7/26  
        }

    }

    /**
     * 通过调用QuerySendDetails接口，可以根据短信发送日期查看发送记录和短信内容，也可以添加发送流水号，根据流水号查询指定日期指定请求的发送详情。如果指定日期短信发送量较大，可以分页查看。指定每页显示的短信详情数量和查看的页数，即可分页查看发送记录。
     * <p>
     * QPS限制
     * 本接口的单用户QPS限制为5000次/秒。超过限制，API调用会被限流，这可能会影响您的业务，请合理调用。
     *
     * @param request
     * @throws Throwable
     */
    protected void querySendDetails(QuerySendDetailsRequest request) throws Throwable {
        QuerySendDetailsResponse response = client.querySendDetails(request);
        // TODO: 2023/7/26
    }

    /******************************签名管理*********************************************************************************************************************************/

    /**
     * 您可以通过AddSmsSign接口或短信服务控制台申请短信签名，签名需要符合短信签名规范。您可以通过QuerySmsSign接口或控制台查询签名审核状态。
     * 更多短信签名审核信息，请参见使用须知。
     * QPS限制
     * 本接口的单用户QPS限制为1次/秒。超过限制，API调用会被限流，这可能会影响您的业务，请合理调用。
     *
     * @param request
     * @throws Throwable
     */
    protected void querySendDetails(AddSmsSignRequest request) throws Throwable {
        AddSmsSignResponse response = client.addSmsSign(request);
        // TODO: 2023/7/26
    }

    /**
     * 接口说明
     * 不支持删除正在审核中的签名。
     * 短信签名删除后不可恢复，请谨慎操作。
     * QPS限制
     * 本接口的单用户QPS限制为1000次/秒。超过限制，API调用会被限流，这可能会影响您的业务，请合理调用。
     *
     * @param request
     * @throws Throwable
     */
    protected void deleteSmsSign(DeleteSmsSignRequest request) throws Throwable {
        DeleteSmsSignResponse response = client.deleteSmsSign(request);
        // TODO: 2023/7/26
    }

    protected void modifySmsSign(ModifySmsSignRequest request) throws Throwable {
        ModifySmsSignResponse response = client.modifySmsSign(request);
        // TODO: 2023/7/26
    }

    protected void querySmsSignList(QuerySmsSignListRequest request) throws Throwable {
        QuerySmsSignListResponse response = client.querySmsSignList(request);
        // TODO: 2023/7/26
    }

    protected void querySmsSign(QuerySmsSignRequest request) throws Throwable {
        QuerySmsSignResponse response = client.querySmsSign(request);
        // TODO: 2023/7/26
    }


    @Override
    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);
        return statuses.stream().map(status -> {
            SmsReceiveRespDTO resp = new SmsReceiveRespDTO();
            resp.setSuccess(status.getSuccess());
            resp.setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg());
            resp.setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime());
            resp.setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()));
            return resp;
        }).collect(Collectors.toList());
    }


    @Override
    protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
        return null;
    }

    <Q, P, R> SmsCommonResult<R> invoke(Supplier<Q> supplierRequest, SdkFunction<Q, P> sdkFunction, Function<P, SmsCommonResult<R>> result) {
        // 构建请求body
        Q request = supplierRequest.get();
        // 调用阿里云发送短信
        P response = null;
        try {
            response = sdkFunction.apply(request);
        } catch (Exception e) {
            // 调用异常，封装结果
            return SmsCommonResult.build(EXCEPTION.getCode().toString(), e.getMessage(), null, null, codeMapping);
        }
        return result.apply(response);
    }


    /**
     * 短信接收状态
     * <p>
     * 参见 https://help.aliyun.com/document_detail/101867.html 文档
     *
     * @author 芋道源码
     */
    @Data
    public static class SmsReceiveStatus {

        /**
         * 手机号
         */
        @JsonProperty("phone_number")
        private String phoneNumber;
        /**
         * 发送时间
         */
        @JsonProperty("send_time")
        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
        private LocalDateTime sendTime;
        /**
         * 状态报告时间
         */
        @JsonProperty("report_time")
        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
        private LocalDateTime reportTime;
        /**
         * 是否接收成功
         */
        private Boolean success;
        /**
         * 状态报告说明
         */
        @JsonProperty("err_msg")
        private String errMsg;
        /**
         * 状态报告编码
         */
        @JsonProperty("err_code")
        private String errCode;
        /**
         * 发送序列号
         */
        @JsonProperty("biz_id")
        private String bizId;
        /**
         * 用户序列号
         * <p>
         * 这里我们传递的是 SysSmsLogDO 的日志编号
         */
        @JsonProperty("out_id")
        private String outId;
        /**
         * 短信长度，例如说 1、2、3
         * <p>
         * 140 字节算一条短信，短信长度超过 140 字节时会拆分成多条短信发送
         */
        @JsonProperty("sms_size")
        private Integer smsSize;

    }

    private interface SdkFunction<T, R> {
        /**
         * 执行
         *
         * @param t
         */
        R apply(T t) throws Exception;
    }
}
